const dayjs = require('dayjs');
// Git Graph风格的项目图谱Canvas绘制组件
Component({
  properties: {
    // 节点数据
    graphData: {
      type: Array,
      value: [],
      observer: function(newVal) {
        if (newVal && newVal.length) {
          this.setData({
            isLoad: true
          });
          this._drawGraphAfterDataChange();
        }
      }
    },
    // 视图配置
    config: {
      type: Object,
      value: {
        nodeRadius: 8,      // 节点圆点半径
        xSpacing: 150,      // 节点水平间距（从120增加到150）
        ySpacing: 85,       // 节点垂直间距（略微增加）
        laneWidth: 80,      // 分支轨道宽度（从60增加到80）
        timelineWidth: 3,   // 时间线宽度
        branchLineWidth: 3, // 分支线宽度
        mergeCurveRadius: 15, // 合并曲线半径
      }
    }
  },

  data: {
    isLoad: false,         // 是否已加载
    canvas: null,          // Canvas实例
    ctx: null,             // Canvas上下文
    canvasWidth: 0,        // Canvas宽度
    canvasHeight: 0,       // Canvas高度
    scale: 1,              // 当前缩放比例
    scalePercent: '100',   // 缩放百分比显示值
    offsetX: 0,            // 水平偏移量
    offsetY: 0,            // 垂直偏移量
    startX: 0,             // 触摸起始点X
    startY: 0,             // 触摸起始点Y
    lastX: 0,              // 上次触摸点X
    lastY: 0,              // 上次触摸点Y
    isTouching: false,     // 是否正在触摸
    nodes: [],             // 处理后的节点数据
    branchColors: {        // 分支颜色映射
      'main': '#3b8eea',    // 主分支 - 蓝色
      'feature': '#26a69a', // 功能分支 - 绿色
      'hotfix': '#ff5252',  // 修复分支 - 红色
      'release': '#f28c35'  // 发布分支 - 橙色
    },
    selectedNode: null,    // 当前选中的节点
    showDetailLabels: true, // 是否显示详细标签
    lastWheelTime: 0,      // 上次滚轮事件时间戳
    wheelDebounceTime: 100, // 滚轮事件防抖时间
    isZooming: false,      // 是否显示缩放指示器
    zoomIndicatorTimer: null, // 缩放指示器计时器
    
    // 双指缩放相关数据
    lastTouchDistance: 0,   // 上次双指触摸间距离
    isPinching: false,      // 是否正在双指缩放
    touchDebounceTime: 100, // 双指缩放防抖时间
    lastPinchTime: 0,       // 上次双指缩放时间戳
    showPinchHint: false,   // 是否显示双指缩放提示
    
    // 全屏相关
    isFullscreen: false,    // 是否全屏显示
    originalStyle: null     // 保存原始样式信息
  },

  lifetimes: {
    attached: function() {
      // 组件加载后初始化Canvas
      this._initCanvas();
      
      // 显示双指缩放提示
      this._showPinchToZoomHint();
    },
    detached: function() {
      // 组件卸载时的清理工作
      // 清除计时器
      if (this.data.zoomIndicatorTimer) {
        clearTimeout(this.data.zoomIndicatorTimer);
      }
      if (this.data.pinchHintTimer) {
        clearTimeout(this.data.pinchHintTimer);
      }
    }
  },

  methods: {
    /**
     * 初始化Canvas
     */
    _initCanvas: function() {
      // 使用创建一个新的Promise处理初始化
      const initPromise = new Promise((resolve, reject) => {
        try {
          const query = this.createSelectorQuery();
          query.select('#gitGraphCanvas')
            .fields({ node: true, size: true })
            .exec(res => {
              if (!res || !res[0]) {
                console.error('Canvas节点获取失败，将在500ms后重试');
                // 在失败时reject
                reject('Canvas节点获取失败');
                return;
              }
              
              // 获取Canvas节点和上下文
              const canvas = res[0].node;
              const ctx = canvas.getContext('2d');
              
              // 使用推荐的API获取设备像素比
              let dpr = 1;
              try {
                const deviceInfo = wx.getDeviceInfo();
                dpr = deviceInfo.pixelRatio || 1;
              } catch (error) {
                console.warn('获取设备像素比失败，使用默认值', error);
              }
              
              canvas.width = res[0].width * dpr;
              canvas.height = res[0].height * dpr;
              ctx.scale(dpr, dpr);
              
              // 计算初始缩放百分比
              const initialScale = this.data.isFullscreen ? 1.1 : 0.9;
              const scalePercent = Math.round(initialScale * 100);
              
              // 保存Canvas相关信息
              this.setData({
                canvas: canvas,
                ctx: ctx,
                canvasWidth: res[0].width,
                canvasHeight: res[0].height,
                // 调整初始偏移，使图谱更靠左显示，留出更多空间给文本标签
                offsetX: 40, // 原来是 res[0].width / 4，现在减小水平偏移，让节点靠左
                offsetY: 80, // 增加垂直偏移，给顶部留出更多空间
                scale: initialScale, // 略微缩小初始视图，使内容更紧凑
                scalePercent: scalePercent.format("YYYY-MM-DD HH:mm:ss")
              });
              
              // 根据全屏状态调整视图范围
              if (this.data.isFullscreen) {
                // 全屏模式下，可以调整偏移量，使视图更加居中
                this.setData({
                  offsetY: 100, // 给顶部留出更多空间
                });
              }
              
              // 解析Promise
              resolve();
            });
        } catch (err) {
          console.error('Canvas初始化出错:', err);
          reject(err);
        }
      });
      
      // 处理Promise
      initPromise.then(() => {
        // 如果已有数据，初始化后立即绘制
        if (this.data.graphData && this.data.graphData.length) {
          this._drawGraphAfterDataChange();
        }
      }).catch(err => {
        // 失败时重试
        setTimeout(() => this._initCanvas(), 500);
      });
    },
    
    /**
     * 数据变更后重新绘制图谱
     */
    _drawGraphAfterDataChange: function() {
      // 确保Canvas已初始化
      if (!this.data.ctx) {
        setTimeout(() => this._drawGraphAfterDataChange(), 100);
        return;
      }
      
      // 处理数据
      this._processGraphData();
      
      // 执行绘制
      this._drawGraph();
    },
    
    /**
     * 处理图谱数据，预先计算节点位置
     */
    _processGraphData: function() {
      const { xSpacing, ySpacing, laneWidth } = this.data.config;
      const graphData = this.data.graphData || [];
      
      // 深拷贝数据，避免直接修改原始数据
      const nodes = JSON.parse(JSON.stringify(graphData));
      
      // 计算每个节点的位置
      nodes.forEach((node, index) => {
        // 处理日期格式化 - 确保日期格式一致
        if (typeof node.date === 'string') {
          node.date = node.date.trim();
        }
        
        // 使用原始索引，最新的（数组开头的）显示在最上方
        const nodeIndex = index;
        
        // X轴位置取决于分支索引，Y轴位置按照数组中的顺序（时间顺序）
        // 增加分支间的水平间距，使文本更不容易重叠
        const increasedLaneWidth = laneWidth * 1.5; // 将分支水平间距增加50%
        node.x = 80 + (increasedLaneWidth * node.branchIndex); // 同时增加起始X位置
        node.y = 50 + (ySpacing * nodeIndex);
        
        // 初始化节点绘制位置（根据缩放和偏移处理）
        node.drawX = node.x;
        node.drawY = node.y;
        
        // 为节点添加半径属性
        node.radius = this.data.config.nodeRadius;
        
        // 获取分支颜色（如果未指定）
        if (!node.color) {
          const branchType = this._getBranchType(node.branch);
          node.color = this.data.branchColors[branchType] || '#888888';
        }
      });
      
      this.setData({ nodes });
    },
    
    /**
     * 根据分支名称获取分支类型
     */
    _getBranchType: function(branchName) {
      if (!branchName) return 'main';
      
      branchName = branchName.toLowerCase();
      
      if (branchName === 'main' || branchName === 'master') {
        return 'main';
      } else if (branchName.includes('feature') || branchName.includes('feat')) {
        return 'feature';
      } else if (branchName.includes('hotfix') || branchName.includes('fix')) {
        return 'hotfix';
      } else if (branchName.includes('release') || branchName.includes('rel')) {
        return 'release';
      }
      
      return 'feature'; // 默认为功能分支
    },
    
    /**
     * 绘制图谱
     */
    _drawGraph: function() {
      if (!this.data.ctx || !this.data.nodes.length) return;
      
      const { ctx, canvasWidth, canvasHeight, scale, offsetX, offsetY, nodes } = this.data;
      const { nodeRadius, timelineWidth, branchLineWidth } = this.data.config;
      
      // 清除画布
      ctx.clearRect(0, 0, canvasWidth, canvasHeight);
      
      // 应用缩放和平移变换
      ctx.save();
      ctx.translate(offsetX, offsetY);
      ctx.scale(scale, scale);
      
      // 1. 先绘制分支连接线
      this._drawBranchLines();
      
      // 2. 绘制合并曲线
      this._drawMergeConnections();
      
      // 3. 绘制节点及文本标签
      nodes.forEach(node => {
        this._drawNode(node);
        this._drawNodeLabel(node);
      });
      
      // 如果有选中节点，绘制高亮效果
      if (this.data.selectedNode) {
        this._drawSelectedNodeHighlight();
      }
      
      // 恢复画布状态
      ctx.restore();
    },
    
    /**
     * 绘制节点文本标签
     */
    _drawNodeLabel: function(node) {
      const { ctx, showDetailLabels } = this.data;
      const textMargin = 10; // 文本与节点的间距
      const maxTextWidth = 250; // 最大文本宽度，超过则截断
      
      ctx.save();
      
      // 设置文本样式
      ctx.font = '14px Arial';
      ctx.fillStyle = '#333333';
      ctx.textBaseline = 'middle';
      
      // 根据是否为最新节点调整文本位置
      let yOffset = 0;
      
      // 如果是最新节点，则分支标签在节点上方
      if (node.isLatestNode) {
        // 由于分支标签已移至节点上方，不再需要特殊处理
      }
      
      // 处理可能过长的描述文本
      let displayDescription = node.description;
      const descWidth = ctx.measureText(displayDescription).width;
      
      // 如果文本过长，进行截断并添加省略号
      if (descWidth > maxTextWidth) {
        let truncatedText = displayDescription;
        let truncatedWidth = descWidth;
        
        // 逐步截断文本直到满足最大宽度要求
        while (truncatedWidth > maxTextWidth && truncatedText.length > 3) {
          truncatedText = truncatedText.slice(0, -1);
          truncatedWidth = ctx.measureText(truncatedText + '...').width;
        }
        
        displayDescription = truncatedText + '...';
      }
      
      // 绘制节点描述
      ctx.fillText(displayDescription, node.x + node.radius + textMargin, node.y + yOffset);
      
      // 如果显示详细信息，绘制额外的详情
      if (showDetailLabels) {
        // 绘制小号文本 - 日期和操作员
        ctx.font = '12px Arial';
        ctx.fillStyle = '#666666';
        const detailsText = `${node.date} · ${node.operator}`;
        
        // 处理可能过长的详情文本
        let displayDetails = detailsText;
        const detailsWidth = ctx.measureText(displayDetails).width;
        
        if (detailsWidth > maxTextWidth) {
          let truncatedText = displayDetails;
          let truncatedWidth = detailsWidth;
          
          while (truncatedWidth > maxTextWidth && truncatedText.length > 3) {
            truncatedText = truncatedText.slice(0, -1);
            truncatedWidth = ctx.measureText(truncatedText + '...').width;
          }
          
          displayDetails = truncatedText + '...';
        }
        
        ctx.fillText(displayDetails, node.x + node.radius + textMargin, node.y + 20 + yOffset);
        
        // 如果有状态ID，也显示
        if (node.stateId) {
          ctx.fillText(`ID: ${node.stateId}`, node.x + node.radius + textMargin, node.y + 40 + yOffset);
        }
      }
      
      ctx.restore();
    },
    
    /**
     * 绘制分支线
     */
    _drawBranchLines: function() {
      const { ctx, nodes } = this.data;
      const { timelineWidth } = this.data.config;
      
      if (nodes.length === 0) return;
      
      // 按分支分组整理节点
      const branchGroups = {};
      nodes.forEach(node => {
        if (!branchGroups[node.branch]) {
          branchGroups[node.branch] = [];
        }
        branchGroups[node.branch].push(node);
      });
      
      // 对每个分支绘制垂直线
      Object.keys(branchGroups).forEach(branch => {
        const branchNodes = branchGroups[branch];
        if (branchNodes.length === 0) return;
        
        // 按Y坐标升序排序
        branchNodes.sort((a, b) => a.y - b.y);
        
        const firstNode = branchNodes[0];
        const lastNode = branchNodes[branchNodes.length - 1];
        
        // 绘制分支线
        ctx.beginPath();
        ctx.strokeStyle = firstNode.color;
        ctx.lineWidth = timelineWidth;
        
        // 如果只有一个节点，不需要绘制垂直线
        if (branchNodes.length === 1) return;
        
        // 绘制从第一个节点到最后一个节点的线
        ctx.moveTo(firstNode.x, firstNode.y);
        
        // 如果分支有多个节点，依次连接
        for (let i = 1; i < branchNodes.length; i++) {
          ctx.lineTo(branchNodes[i].x, branchNodes[i].y);
        }
        
        ctx.stroke();
      });
      
      // 绘制分支节点和父分支之间的连接线
      nodes.forEach(node => {
        if (node.nodeType === 'branch' && node.parentBranch) {
          // 查找父分支中最接近的节点
          const parentNodes = branchGroups[node.parentBranch] || [];
          if (parentNodes.length === 0) return;
          
          // 找到时间上最接近的父节点（Y坐标最接近的）
          const parentNode = parentNodes.reduce((closest, current) => {
            if (!closest) return current;
            return Math.abs(current.y - node.y) < Math.abs(closest.y - node.y) ? current : closest;
          }, null);
          
          if (!parentNode) return;
          
          // 绘制从父节点到分支节点的连接线
          this._drawBranchConnection(parentNode, node);
        }
      });
    },
    
    /**
     * 绘制分支连接线
     */
    _drawBranchConnection: function(parentNode, branchNode) {
      const { ctx } = this.data;
      const { branchLineWidth } = this.data.config;
      
      // 绘制水平连接线和贝塞尔曲线
      ctx.beginPath();
      ctx.strokeStyle = branchNode.color;
      ctx.lineWidth = branchLineWidth;
      
      const startX = parentNode.x;
      const startY = parentNode.y;
      const endX = branchNode.x;
      const endY = branchNode.y;
      
      // 使用贝塞尔曲线连接
      // 计算控制点位置
      const controlPointY = (startY + endY) / 2;
      
      ctx.moveTo(startX, startY);
      
      // 3次贝塞尔曲线 - 平滑连接
      ctx.bezierCurveTo(
        startX, controlPointY, // 第一个控制点
        endX, controlPointY,   // 第二个控制点
        endX, endY            // 终点
      );
      
      ctx.stroke();
    },
    
    /**
     * 绘制合并连接
     */
    _drawMergeConnections: function() {
      const { ctx, nodes } = this.data;
      const { branchLineWidth } = this.data.config;
      
      // 处理所有的合并连接
      nodes.forEach(node => {
        if (node.nodeType !== 'merge' || !node.connections) return;
        
        // 找出合并连接
        const mergeConnections = node.connections.filter(c => c.type === 'merge');
        
        mergeConnections.forEach(conn => {
          // 找到源节点
          const sourceNode = nodes.find(n => n.id === conn.from);
          if (!sourceNode) return;
          
          // 绘制贝塞尔曲线连接
          ctx.beginPath();
          ctx.strokeStyle = conn.color || node.color;
          ctx.lineWidth = branchLineWidth;
          
          // 计算控制点（为了平滑的曲线）
          const startX = sourceNode.x;
          const startY = sourceNode.y;
          const endX = node.x;
          const endY = node.y;
          
          // 计算控制点，实现平滑的曲线
          const midY = (startY + endY) / 2;
          
          // 使用三次贝塞尔曲线
          ctx.moveTo(startX, startY);
          
          // 正确设置贝塞尔曲线的控制点
          const cp1x = startX; // 第一个控制点保持X坐标不变
          const cp1y = midY;   // 第一个控制点的Y坐标在中间
          const cp2x = endX;   // 第二个控制点保持与终点X坐标相同
          const cp2y = midY;   // 第二个控制点的Y坐标在中间
          
          ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, endX, endY);
          
          // 描边
          ctx.stroke();
        });
      });
    },
    
    /**
     * 绘制节点
     */
    _drawNode: function(node) {
      const { ctx } = this.data;
      const { nodeRadius } = this.data.config;
      
      ctx.save();
      
      // 绘制节点圆点
      ctx.beginPath();
      ctx.fillStyle = node.color;
      ctx.strokeStyle = '#ffffff';
      ctx.lineWidth = 2;
      
      // 根据节点类型绘制不同样式
      if (node.nodeType === 'merge') {
        // 合并节点 - 菱形
        ctx.save();
        ctx.translate(node.x, node.y);
        ctx.rotate(Math.PI / 4); // 旋转45度
        ctx.rect(-nodeRadius, -nodeRadius, nodeRadius * 2, nodeRadius * 2);
        ctx.fillStyle = '#ffffff';
        ctx.fill();
        ctx.stroke();
        
        // 绘制交叉线
        ctx.beginPath();
        ctx.strokeStyle = node.color;
        ctx.lineWidth = 1.5;
        ctx.moveTo(-nodeRadius, 0);
        ctx.lineTo(nodeRadius, 0);
        ctx.moveTo(0, -nodeRadius);
        ctx.lineTo(0, nodeRadius);
        ctx.stroke();
        ctx.restore();
      } else if (node.nodeType === 'branch') {
        // 分支节点 - 空心圆
        ctx.arc(node.x, node.y, nodeRadius, 0, Math.PI * 2);
        ctx.fillStyle = '#ffffff';
        ctx.fill();
        ctx.stroke();
        
        // 添加边框
        ctx.beginPath();
        ctx.strokeStyle = node.color;
        ctx.lineWidth = 2;
        ctx.arc(node.x, node.y, nodeRadius, 0, Math.PI * 2);
        ctx.stroke();
      } else {
        // 普通节点 - 实心圆
        ctx.arc(node.x, node.y, nodeRadius, 0, Math.PI * 2);
        ctx.fill();
        ctx.stroke();
      }
      
      // 绘制分支标签（如果是最新节点）
      if (node.isLatestNode) {
        this._drawBranchTag(node);
      }
      
      ctx.restore();
    },
    
    /**
     * 绘制分支标签
     */
    _drawBranchTag: function(node) {
      const { ctx } = this.data;
      const tagPadding = 5;
      const tagHeight = 20;
      
      // 测量文本宽度
      ctx.font = '12px Arial';
      const textWidth = ctx.measureText(node.branch).width;
      const tagWidth = textWidth + tagPadding * 2;
      
      // 修改标签位置，避免与节点描述重叠
      const tagX = node.x - tagWidth / 2; // 居中对齐节点
      const tagY = node.y - tagHeight - 15; // 上移更多，避免与描述文本重叠
      
      // 绘制从标签到节点的连接线
      ctx.beginPath();
      ctx.strokeStyle = node.color;
      ctx.lineWidth = 1.5;
      ctx.setLineDash([2, 2]); // 虚线效果
      
      // 绘制连接线（从标签底部中心到节点顶部）
      ctx.moveTo(node.x, tagY + tagHeight);
      ctx.lineTo(node.x, node.y - node.radius - 2);
      ctx.stroke();
      ctx.setLineDash([]); // 重置虚线样式
      
      // 绘制小三角形箭头指向节点
      ctx.beginPath();
      ctx.fillStyle = node.color;
      const arrowSize = 5;
      ctx.moveTo(node.x, node.y - node.radius - 2);
      ctx.lineTo(node.x - arrowSize, node.y - node.radius - 2 - arrowSize);
      ctx.lineTo(node.x + arrowSize, node.y - node.radius - 2 - arrowSize);
      ctx.closePath();
      ctx.fill();
      
      // 绘制标签背景（圆角矩形）
      ctx.beginPath();
      ctx.fillStyle = node.color;
      this._drawRoundedRect(tagX, tagY, tagWidth, tagHeight, 5);
      ctx.fill();
      
      // 绘制标签文本
      ctx.fillStyle = '#ffffff';
      ctx.fillText(node.branch, tagX + tagPadding, tagY + 14);
    },
    
    /**
     * 绘制选中节点的高亮效果
     */
    _drawSelectedNodeHighlight: function() {
      const { ctx, selectedNode, showDetailLabels } = this.data;
      
      if (!selectedNode) return;
      
      // 绘制高亮环
      ctx.beginPath();
      ctx.strokeStyle = selectedNode.color;
      ctx.lineWidth = 3;
      ctx.setLineDash([5, 3]);
      ctx.arc(selectedNode.x, selectedNode.y, selectedNode.radius + 5, 0, Math.PI * 2);
      ctx.stroke();
      ctx.setLineDash([]);
      
      // 如果显示详细信息，绘制详细信息框
      if (showDetailLabels) {
        // 绘制详细信息框
        const boxPadding = 10;
        const boxWidth = 300;
        const boxHeight = 120;
        const boxX = selectedNode.x + 20;
        const boxY = selectedNode.y - boxHeight / 2;
        
        // 绘制信息框背景
        ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
        ctx.strokeStyle = selectedNode.color;
        ctx.lineWidth = 2;
        
        // 绘制圆角矩形
        this._drawRoundedRect(boxX, boxY, boxWidth, boxHeight, 8);
        ctx.fill();
        ctx.stroke();
        
        // 绘制节点详细信息
        ctx.fillStyle = '#333333';
        ctx.font = 'bold 16px Arial';
        ctx.fillText(selectedNode.description, boxX + boxPadding, boxY + 30);
        
        ctx.fillStyle = '#666666';
        ctx.font = '14px Arial';
        ctx.fillText(`日期: ${selectedNode.date}`, boxX + boxPadding, boxY + 60);
        ctx.fillText(`操作员: ${selectedNode.operator}`, boxX + boxPadding, boxY + 85);
        ctx.fillText(`状态ID: ${selectedNode.stateId || '-'}`, boxX + boxPadding, boxY + 110);
      }
    },
    
    /**
     * 绘制圆角矩形
     */
    _drawRoundedRect: function(x, y, width, height, radius) {
      const { ctx } = this.data;
      ctx.beginPath();
      ctx.moveTo(x + radius, y);
      ctx.lineTo(x + width - radius, y);
      ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
      ctx.lineTo(x + width, y + height - radius);
      ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
      ctx.lineTo(x + radius, y + height);
      ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
      ctx.lineTo(x, y + radius);
      ctx.quadraticCurveTo(x, y, x + radius, y);
      ctx.closePath();
    },
    
    /**
     * 重置视图
     */
    resetView: function() {
      // 根据是否全屏设置不同的初始视图参数
      const newScale = this.data.isFullscreen ? 1.1 : 0.9;
      const scalePercent = Math.round(newScale * 100);
      
      if (this.data.isFullscreen) {
        this.setData({
          scale: newScale,
          scalePercent: scalePercent.format("YYYY-MM-DD HH:mm:ss"),
          offsetX: 40,
          offsetY: 100
        });
      } else {
        this.setData({
          scale: newScale,
          scalePercent: scalePercent.format("YYYY-MM-DD HH:mm:ss"),
          offsetX: 40,
          offsetY: 80
        });
      }
      
      // 显示重置提示
      wx.showToast({
        title: '视图已重置',
        icon: 'none',
        duration: 1500
      });
      
      // 重绘图谱
      this._drawGraph();
    },
    
    /**
     * 处理滚轮事件 - 用于缩放图谱
     */
    onWheelScroll: function(e) {
      // 防抖处理，避免频繁触发
      const now = dayjs().valueOf();
      if (now - this.data.lastWheelTime < this.data.wheelDebounceTime) {
        return;
      }
      this.setData({ lastWheelTime: now });
      
      // 获取滚轮增量，deltaY > 0表示向下滚动，deltaY < 0表示向上滚动
      const deltaY = e.detail.deltaY;
      
      // 缩放系数，滚轮效果不要太激烈
      const zoomFactor = 0.1;
      let newScale = this.data.scale;
      
      if (deltaY > 0) {
        // 向下滚动，缩小
        newScale = Math.max(this.data.scale * (1 - zoomFactor), 0.5);
      } else {
        // 向上滚动，放大
        newScale = Math.min(this.data.scale * (1 + zoomFactor), 3);
      }
      
      // 计算缩放百分比
      const scalePercent = Math.round(newScale * 100);
      
      // 计算鼠标位置相对于Canvas的坐标
      let mouseX = e.detail.clientX;
      let mouseY = e.detail.clientY;
      
      // 如果没有提供鼠标位置，使用Canvas中心点
      if (mouseX === undefined || mouseY === undefined) {
        mouseX = this.data.canvasWidth / 2;
        mouseY = this.data.canvasHeight / 2;
      }
      
      // 获取Canvas在页面中的位置
      const query = this.createSelectorQuery();
      query.select('#gitGraphCanvas')
        .boundingClientRect()
        .exec(res => {
          if (!res || !res[0]) return;
          
          const rect = res[0];
          const canvasX = mouseX - rect.left;
          const canvasY = mouseY - rect.top;
          
          // 计算缩放前后鼠标位置在图谱坐标系中的坐标
          const oldGraphX = (canvasX - this.data.offsetX) / this.data.scale;
          const oldGraphY = (canvasY - this.data.offsetY) / this.data.scale;
          
          // 更新缩放比例
          this.setData({ 
            scale: newScale,
            scalePercent: scalePercent.format("YYYY-MM-DD HH:mm:ss")
          });
          
          // 计算新的偏移量，保持鼠标位置不变
          const newOffsetX = canvasX - oldGraphX * newScale;
          const newOffsetY = canvasY - oldGraphY * newScale;
          
          this.setData({
            offsetX: newOffsetX,
            offsetY: newOffsetY
          });
          
          // 显示缩放指示器
          this._showZoomIndicator();
          
          // 重绘图谱
          this._drawGraph();
        });
    },
    
    /**
     * 显示缩放指示器
     */
    _showZoomIndicator: function() {
      // 清除旧的计时器
      if (this.data.zoomIndicatorTimer) {
        clearTimeout(this.data.zoomIndicatorTimer);
      }
      
      // 显示指示器
      this.setData({ isZooming: true });
      
      // 设置新的计时器，2秒后隐藏指示器
      const timer = setTimeout(() => {
        this.setData({ isZooming: false });
      }, 2000);
      
      // 保存计时器ID
      this.setData({ zoomIndicatorTimer: timer });
    },
    
    /**
     * 触摸开始事件
     */
    onTouchStart: function(e) {
      // 单指触摸 - 移动视图或选择节点
      if (e.touches.length === 1) {
        const touch = e.touches[0];
        
        // 记录当前时间，用于区分点击和拖动
        const touchStartTime = dayjs().valueOf();
        
        // 记录触摸状态
        this.setData({
          isTouching: true,
          isPinching: false,
          startX: touch.clientX,
          startY: touch.clientY,
          lastX: touch.clientX,
          lastY: touch.clientY,
          touchStartTime: touchStartTime,
          touchMoved: false // 标记是否发生了移动
        });
        
        // 存储触摸位置，在触摸结束时再判断是否是点击
        this.touchInfo = {
          clientX: touch.clientX,
          clientY: touch.clientY
        };
      } 
      // 双指触摸 - 缩放
      else if (e.touches.length === 2) {
        // 计算双指之间的距离
        const touch1 = e.touches[0];
        const touch2 = e.touches[1];
        const distance = this._getTouchDistance(touch1, touch2);
        
        this.setData({
          isTouching: false,
          isPinching: true,
          lastTouchDistance: distance
        });
      }
    },
    
    /**
     * 触摸移动事件
     */
    onTouchMove: function(e) {
      // 单指移动 - 平移视图
      if (this.data.isTouching && e.touches.length === 1) {
        const touch = e.touches[0];
        const deltaX = touch.clientX - this.data.lastX;
        const deltaY = touch.clientY - this.data.lastY;
        
        // 如果移动距离超过5像素，标记为已移动
        if (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5) {
          this.setData({ touchMoved: true });
        }
        
        this.setData({
          offsetX: this.data.offsetX + deltaX,
          offsetY: this.data.offsetY + deltaY,
          lastX: touch.clientX,
          lastY: touch.clientY
        });
        
        this._drawGraph();
      }
      // 双指移动 - 缩放视图
      else if (this.data.isPinching && e.touches.length === 2) {
        // 防抖处理，避免频繁触发
        const now = dayjs().valueOf();
        if (now - this.data.lastPinchTime < this.data.touchDebounceTime) {
          return;
        }
        this.setData({ lastPinchTime: now });
        
        // 计算当前双指间距离
        const touch1 = e.touches[0];
        const touch2 = e.touches[1];
        const currentDistance = this._getTouchDistance(touch1, touch2);
        
        // 计算缩放比例变化
        const scaleFactor = currentDistance / this.data.lastTouchDistance;
        
        // 只有当缩放比例变化足够明显时才进行缩放
        if (Math.abs(scaleFactor - 1) > 0.05) {
          // 计算缩放中心点 (双指中点)
          const centerX = (touch1.clientX + touch2.clientX) / 2;
          const centerY = (touch1.clientY + touch2.clientY) / 2;
          
          // 获取Canvas在页面中的位置
          const query = this.createSelectorQuery();
          query.select('#gitGraphCanvas')
            .boundingClientRect()
            .exec(res => {
              if (!res || !res[0]) return;
              
              const rect = res[0];
              const canvasX = centerX - rect.left;
              const canvasY = centerY - rect.top;
              
              // 计算缩放前后鼠标位置在图谱坐标系中的坐标
              const oldGraphX = (canvasX - this.data.offsetX) / this.data.scale;
              const oldGraphY = (canvasY - this.data.offsetY) / this.data.scale;
              
              // 计算新的缩放比例，限制在合理范围内
              let newScale = this.data.scale * scaleFactor;
              newScale = Math.max(0.5, Math.min(newScale, 3)); // 限制缩放范围
              
              // 计算缩放百分比
              const scalePercent = Math.round(newScale * 100);
              
              // 更新缩放比例
              this.setData({ 
                scale: newScale,
                scalePercent: scalePercent.format("YYYY-MM-DD HH:mm:ss"),
                lastTouchDistance: currentDistance
              });
              
              // 计算新的偏移量，保持缩放中心点不变
              const newOffsetX = canvasX - oldGraphX * newScale;
              const newOffsetY = canvasY - oldGraphY * newScale;
              
              this.setData({
                offsetX: newOffsetX,
                offsetY: newOffsetY
              });
              
              // 显示缩放指示器
              this._showZoomIndicator();
              
              // 重绘图谱
              this._drawGraph();
            });
        }
      }
    },
    
    /**
     * 触摸结束事件
     */
    onTouchEnd: function(e) {
      // 检查是否是短暂触摸（点击）
      const touchEndTime = dayjs().valueOf();
      const touchDuration = touchEndTime - (this.data.touchStartTime || 0);
      
      // 短暂触摸（小于250ms）且没有明显移动，视为点击
      if (touchDuration < 250 && !this.data.touchMoved && this.touchInfo) {
        console.log('触摸结束，检测为点击，触摸时长:', touchDuration);
        this._directCheckNodeSelection(this.touchInfo.clientX, this.touchInfo.clientY);
      }
      
      // 重置触摸状态
      this.setData({ 
        isTouching: false,
        isPinching: false,
        touchMoved: false
      });
      
      // 清理临时触摸信息
      this.touchInfo = null;
    },
    
    /**
     * 计算两个触摸点之间的距离
     */
    _getTouchDistance: function(touch1, touch2) {
      const dx = touch1.clientX - touch2.clientX;
      const dy = touch1.clientY - touch2.clientY;
      return Math.sqrt(dx * dx + dy * dy);
    },
    
    /**
     * 显示双指缩放提示
     */
    _showPinchToZoomHint: function() {
      // 显示提示
      this.setData({ showPinchHint: true });
      
      // 设置定时器，3秒后隐藏提示
      const pinchHintTimer = setTimeout(() => {
        this.setData({ showPinchHint: false });
      }, 3000);
      
      // 保存计时器ID
      this.setData({ pinchHintTimer });
    },
    
    /**
     * 切换全屏显示
     */
    toggleFullscreen: function() {
      const isFullscreen = !this.data.isFullscreen;
      const newScale = isFullscreen ? 1.1 : 0.9;
      const scalePercent = Math.round(newScale * 100);
      
      if (isFullscreen) {
        // 进入全屏
        this.setData({
          isFullscreen: true,
          scale: newScale,
          scalePercent: scalePercent.format("YYYY-MM-DD HH:mm:ss")
        });
        
        // 触发页面全屏事件，让页面处理全屏逻辑
        this.triggerEvent('fullscreen', { isFullscreen: true });
      } else {
        // 退出全屏
        this.setData({
          isFullscreen: false,
          scale: newScale,
          scalePercent: scalePercent.format("YYYY-MM-DD HH:mm:ss")
        });
        
        // 触发页面全屏事件，让页面处理全屏逻辑
        this.triggerEvent('fullscreen', { isFullscreen: false });
      }
      
      // 延迟执行Canvas初始化，确保DOM已更新完成
      setTimeout(() => {
        this._initCanvas();
        
        // 重新绘制图谱
        this._drawGraph();
        
        // 显示状态提示
        wx.showToast({
          title: isFullscreen ? '已进入全屏模式' : '已退出全屏模式',
          icon: 'none',
          duration: 1500
        });
      }, 300);
    },
    
    /**
     * 导出图谱为图片
     */
    exportGraph: function() {
      if (!this.data.canvas) {
        wx.showToast({
          title: '画布未初始化',
          icon: 'none'
        });
        return;
      }
      
      wx.showLoading({
        title: '正在导出...',
        mask: true
      });
      
      // 确保先重绘图谱
      this._drawGraph();
      
      try {
        // 导出图片
        wx.canvasToTempFilePath({
          canvas: this.data.canvas,
          success: res => {
            // 保存到相册
            wx.saveImageToPhotosAlbum({
              filePath: res.tempFilePath,
              success: () => {
                wx.hideLoading();
                wx.showToast({
                  title: '已保存到相册',
                  icon: 'success'
                });
              },
              fail: error => {
                wx.hideLoading();
                // 如果是因为用户拒绝授权导致的失败
                if (error.errMsg.indexOf('auth deny') >= 0 || error.errMsg.indexOf('authorize') >= 0) {
                  wx.showModal({
                    title: '提示',
                    content: '需要授权保存到相册',
                    success: res => {
                      if (res.confirm) {
                        wx.openSetting({
                          success: settings => {
                            if (settings.authSetting['scope.writePhotosAlbum']) {
                              // 用户已授权，可以再次尝试保存
                              this.exportGraph();
                            }
                          }
                        });
                      }
                    }
                  });
                } else {
                  wx.showToast({
                    title: '保存失败',
                    icon: 'none'
                  });
                }
              }
            });
          },
          fail: error => {
            wx.hideLoading();
            wx.showToast({
              title: '导出失败: ' + error.errMsg,
              icon: 'none'
            });
          }
        });
      } catch (error) {
        wx.hideLoading();
        wx.showToast({
          title: '导出异常: ' + error.message,
          icon: 'none'
        });
      }
    },
    
    /**
     * 直接检查并处理节点点击 - 更简化的版本
     */
    _directCheckNodeSelection: function(clientX, clientY) {
      try {
        console.log('触发点击检测, 坐标:', clientX, clientY);
        
        // 获取Canvas位置信息
        const query = this.createSelectorQuery();
        query.select('#gitGraphCanvas')
          .boundingClientRect()
          .exec(res => {
            if (!res || !res[0]) {
              console.error('无法获取Canvas位置');
              return;
            }
            
            const rect = res[0];
            // 转换为Canvas内坐标
            const canvasX = clientX - rect.left;
            const canvasY = clientY - rect.top;
            
            // 转换为图谱坐标系
            const graphX = (canvasX - this.data.offsetX) / this.data.scale;
            const graphY = (canvasY - this.data.offsetY) / this.data.scale;
            
            console.log('Canvas内坐标:', canvasX, canvasY);
            console.log('图谱坐标系:', graphX, graphY);
            
            // 增大点击区域，使更容易点中节点
            const hitRadius = this.data.config.nodeRadius * 2; 
            
            // 遍历所有节点检查是否点击
            let clickedNode = null;
            for (let i = 0; i < this.data.nodes.length; i++) {
              const node = this.data.nodes[i];
              const distance = Math.sqrt(
                Math.pow(graphX - node.x, 2) + 
                Math.pow(graphY - node.y, 2)
              );
              
              if (distance <= hitRadius) {
                clickedNode = node;
                console.log('找到点击的节点:', clickedNode);
                break;
              }
            }
            
            // 如果找到点击的节点，直接显示弹窗
            if (clickedNode) {
              console.log('显示节点弹窗:', clickedNode);
              wx.showModal({
                title: '节点详情',
                content: `名称：${clickedNode.description || '未命名节点'}\n` +
                        `日期：${clickedNode.date || '无日期'}\n` +
                        `操作员：${clickedNode.operator || '未知'}\n` + 
                        `分支：${clickedNode.branch || '主分支'}\n` +
                        `${clickedNode.stateId ? '状态ID：' + clickedNode.stateId : ''}`,
                showCancel: false,
                confirmText: '确定',
                confirmColor: '#0066cc'
              });
              
              // 设置选中节点，并触发选中事件
              this.setData({ selectedNode: clickedNode });
              this.triggerEvent('nodeSelect', { node: clickedNode });
              
              // 重绘图谱显示高亮效果
              this._drawGraph();
            } else {
              console.log('未找到点击的节点');
            }
          });
      } catch (err) {
        console.error('节点点击检测出错:', err);
      }
    }
  }
}); 